<template>
  <div :class="['editor-box', self_disabled ? 'editor-disabled' : '']">
    <Toolbar v-if="!hideToolBar" class="editor-toolbar" :editor="editorRef" :default-config="toolbarConfig" :mode="mode" />
    <Editor
      v-model="valueHtml"
      class="editor-content"
      :style="{ height }"
      :mode="mode"
      :default-config="editorConfig"
      @on-created="handleCreated"
      @on-blur="handleBlur"
    />
    <el-dialog v-model="dialogVisible" :destroy-on-close="true" width="1000" top="50px" title="富文本内容预览">
      <div class="editor-content-view" v-html="content"></div>
      <template #footer>
        <el-button @click="dialogVisible = false">取消</el-button>
      </template>
    </el-dialog>
  </div>
</template>

<script setup lang="ts" name="WangEditor">
import { nextTick, computed, inject, shallowRef, onBeforeUnmount, ref } from "vue";
import { IToolbarConfig, IEditorConfig, Boot } from "@wangeditor/editor";
import { Editor, Toolbar } from "@wangeditor/editor-for-vue";
import { uploadImg, uploadVideo } from "@/api/modules/upload";
import "@wangeditor/editor/dist/css/style.css";
import { formContextKey, formItemContextKey } from "element-plus";
import previewMenu from "./preview";
import http from "@/api/services";

const content = ref("");

const dialogVisible = ref(false);

// 富文本 DOM 元素
const editorRef = shallowRef();

// 实列化编辑器
const handleCreated = (editor: any) => {
  editorRef.value = editor;
  if (!editor.getAllMenuKeys().includes("preview")) {
    Boot.registerMenu(previewMenu);
  }

  editorRef.value.on("preview", () => {
    dialogVisible.value = true;
    content.value = valueHtml.value;
  });
};

// 接收父组件参数，并设置默认值
interface RichEditorProps {
  value: string; // 富文本值 ==> 必传
  toolbarConfig?: Partial<IToolbarConfig>; // 工具栏配置 ==> 非必传（默认为空）
  editorConfig?: Partial<IEditorConfig>; // 编辑器配置 ==> 非必传（默认为空）
  height?: string; // 富文本高度 ==> 非必传（默认为 500px）
  mode?: "default" | "simple"; // 富文本模式 ==> 非必传（默认为 default）
  hideToolBar?: boolean; // 是否隐藏工具栏 ==> 非必传（默认为false）
  disabled?: boolean; // 是否禁用编辑器 ==> 非必传（默认为false）
}
const props = withDefaults(defineProps<RichEditorProps>(), {
  value: "",
  toolbarConfig: () => {
    return {
      excludeKeys: [],
      insertKeys: {
        index: 100,
        keys: ["preview"]
      }
    };
  },
  editorConfig: () => {
    return {
      placeholder: "请输入内容...",
      MENU_CONF: {}
    };
  },
  mode: "default",
  hideToolBar: false,
  disabled: false
});

// 获取 el-form 组件上下文
const formContext = inject(formContextKey, void 0);
// 获取 el-form-item 组件上下文
const formItemContext = inject(formItemContextKey, void 0);
// 判断是否禁用上传和删除
const self_disabled = computed(() => {
  return props.disabled || formContext?.disabled;
});

// 判断当前富文本编辑器是否禁用
if (self_disabled.value) nextTick(() => editorRef.value.disable());

// 富文本的内容监听，触发父组件改变，实现双向数据绑定
const emit = defineEmits<{
  "update:value": [value: string];
  "check-validate": [];
}>();
const valueHtml = computed({
  get() {
    return props.value;
  },
  set(val: string) {
    // 防止富文本内容为空时，校验失败
    if (editorRef.value.isEmpty()) val = "";
    emit("update:value", val);
  }
});

/**
 * @description 图片自定义上传
 * @param file 上传的文件
 * @param insertFn 上传成功后的回调函数（插入到富文本编辑器中）
 * */
type InsertFnTypeImg = (url: string, alt?: string, href?: string) => void;
props.editorConfig.MENU_CONF!["uploadImage"] = {
  async customUpload(file: File, insertFn: InsertFnTypeImg) {
    if (!uploadImgValidate(file)) return;
    let formData = new FormData();
    formData.append("file", file);
    try {
      const { data } = await uploadImg(formData);
      insertFn(http.downloadUrl() + data.id);
    } catch (error) {
      console.log(error);
    }
  }
};

// 图片上传前判断
const uploadImgValidate = (file: File): boolean => {
  console.log(file);
  return true;
};

/**
 * @description 视频自定义上传
 * @param file 上传的文件
 * @param insertFn 上传成功后的回调函数（插入到富文本编辑器中）
 * */
type InsertFnTypeVideo = (url: string, poster?: string) => void;
props.editorConfig.MENU_CONF!["uploadVideo"] = {
  async customUpload(file: File, insertFn: InsertFnTypeVideo) {
    if (!uploadVideoValidate(file)) return;
    let formData = new FormData();
    formData.append("file", file);
    try {
      const { data } = await uploadVideo(formData);
      insertFn(http.downloadUrl() + data.id);
    } catch (error) {
      console.log(error);
    }
  }
};

// 视频上传前判断
const uploadVideoValidate = (file: File): boolean => {
  console.log(file);
  return true;
};

// 编辑框失去焦点时触发
const handleBlur = () => {
  formItemContext?.prop && formContext?.validateField([formItemContext.prop as string]);
};

// 组件销毁时，也及时销毁编辑器
onBeforeUnmount(() => {
  if (!editorRef.value) return;
  editorRef.value.destroy();
});

defineExpose({
  editor: editorRef
});
</script>

<style scoped lang="scss">
:deep(.el-dialog__body) {
  height: 480px !important;
  overflow: auto;
}
.editor-box {
  z-index: 2000;
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
  border: 1px solid var(--el-border-color-darker);
}
.editor-toolbar {
  flex: 0 0 auto;
  border-bottom: 1px solid var(--el-border-color-darker);
}
.editor-content {
  flex: 1 1 200px;
  overflow-y: auto;
}
</style>

<style>
.editor-content-view {
  padding: 0 10px;
  margin-top: 20px;
  overflow-x: auto;

  /* border: 3px solid #ccc; */
  border-radius: 5px;
}
.editor-content-view p,
.editor-content-view li {
  white-space: pre-wrap; /* 保留空格 */
}
.editor-content-view blockquote {
  padding: 10px;
  margin: 10px 0;
  background-color: #f1f1f1;
  border-left: 8px solid #d0e5f2;
}
.editor-content-view code {
  padding: 3px;
  font-family: monospace;
  background-color: #eeeeee;
  border-radius: 3px;
}
.editor-content-view pre > code {
  display: block;
  padding: 10px;
}
.editor-content-view table {
  border-collapse: collapse;
}
.editor-content-view td,
.editor-content-view th {
  min-width: 50px;
  height: 20px;
  border: 1px solid #cccccc;
}
.editor-content-view th {
  background-color: #f1f1f1;
}
.editor-content-view ul,
.editor-content-view ol {
  padding-left: 20px;
}
.editor-content-view input[type="checkbox"] {
  margin-right: 5px;
}
</style>
